// 平滑过渡拖拽
interface I_Drag2EventData {
    target: HTMLElement;
    currentTarget: EventTarget;
    start?: {
        evt: PointerEvent,
        targetOffsetX: number,
        targetOffsetY: number,
        pointerPageX: number,
        pointerPageY: number
    };
    move?: {
        evt: PointerEvent,
        pointerDiffX: number,
        pointerDiffY: number,
        targetOffsetX: number,
        targetOffsetY: number
    };
    end?: {
        evt: PointerEvent
    };
    now?: {
        targetOffsetX: number,
        targetOffsetY: number,
        deg: number
    };
}

interface I_Drag2Opts {
    diffSize?: number, // 偏移量超过此值开始拖动
    draggingClassName?: string, // 拖拽中的样式
    dragAnimatingClassName?: string, // 拖拽动画中的样式
    dragAnimatingSpeed?: number,
    restrictParentContainer?: boolean, // 限制在父容器范围内移动
    handle?: string, // 拖拽dom
    xAxis?: boolean, // 允许水平拖拽
    yAxis?: boolean, // 允许垂直拖拽
    onDragStart?: (evt: PointerEvent, data: I_Drag2EventData) => void,
    onDragMove?: (evt: PointerEvent, data: I_Drag2EventData) => void | boolean, // 方法return false时，不执行交互，只返回拖拽数据
    onDragEnd?: (evt: PointerEvent, data: I_Drag2EventData) => void,
    onAnimationFrame?: (data: any)=> void,
    onAnimationFrameEnd?: (data: any)=> void
}

interface I_Drag2Result {
    enable: () => void;
    disable: () => void;
    destroy: () => void;
    setOpts: (opts: I_Drag2Opts) => void;
}

/** 带惯性旋转的拖拽 */
export default function drag(el: string | HTMLElement, opts: I_Drag2Opts): I_Drag2Result | undefined {

    let target: HTMLElement = typeof el === 'string' ? document.querySelector(el)! : el;

    if (!(target instanceof HTMLElement)) {
        return;
    }

    let enabled = true;
    let animationFrameHandle: any; // 存储 requestAnimationFrame 返回值

    opts = {
        diffSize: 0, // 偏移量超过此值开始拖动
        draggingClassName: 'dragging',
        dragAnimatingClassName: 'draganimating',
        dragAnimatingSpeed: 4,
        restrictParentContainer: true, // 限制容器移动
        handle: '', // 拖拽dom
        xAxis: true, // 允许水平拖拽
        yAxis: true, // 允许垂直拖拽
        onDragStart(_evt: PointerEvent, _data: I_Drag2EventData) { },
        onDragMove(_evt: PointerEvent, _data: I_Drag2EventData) { },
        onDragEnd(_evt: PointerEvent, _data: I_Drag2EventData) { },
        onAnimationFrame(_data: any) { },
        onAnimationFrameEnd(_data: any) { },
        ...opts,
    };

    let pointerdown = (evtDown: PointerEvent): void => {

        let currentTarget = evtDown.target as EventTarget;

        if (!enabled || (opts.handle && target.querySelector(opts.handle) !== currentTarget)) {
            return;
        }

        evtDown.preventDefault();
        window.cancelAnimationFrame(animationFrameHandle);

        // 获得初始角度
        let deg = 0;
        let targetTransform: string = document.defaultView!.getComputedStyle(target, null).transform;

        if (targetTransform !== 'none') {
            let targetTransformData: string[] = targetTransform.match(/\((.*)\)/)![1].split(',');
            deg = Math.round(Math.atan2(Number(targetTransformData[1]), Number(targetTransformData[0])) * (180 / Math.PI));
        }

        let data: I_Drag2EventData = {
            target,
            currentTarget,
            start: {
                evt: evtDown,
                targetOffsetX: target.offsetLeft,
                targetOffsetY: target.offsetTop,
                pointerPageX: evtDown.pageX,
                pointerPageY: evtDown.pageY,
            },
            now: {
                targetOffsetX: target.offsetLeft,
                targetOffsetY: target.offsetTop,
                deg
            }
        };

        let isMove = false;

        // 过度函数
        let transition = (): void => {

            // 计算本次动作偏移量
            let diffX: number = (data.move!.targetOffsetX) - data.now!.targetOffsetX;
            let diffY: number = (data.move!.targetOffsetY) - data.now!.targetOffsetY;

            // 计算倾斜角度
            let diffDeg: number = Math.tan(diffX / (Math.abs(data.now!.targetOffsetY - data.move!.targetOffsetY) + Math.sqrt(Math.pow(target.clientHeight, 2) + Math.pow(target.clientWidth, 2)) * 2 + 100 + Math.abs(diffX))) / Math.PI * 180 - data.now!.deg;

            data.now!.targetOffsetX += diffX / opts.dragAnimatingSpeed!;
            data.now!.targetOffsetY += diffY / opts.dragAnimatingSpeed!;
            data.now!.deg += diffDeg / opts.dragAnimatingSpeed!;

            target.style.left = data.now!.targetOffsetX + 'px';
            target.style.top = data.now!.targetOffsetY + 'px';
            target.style.transform = `rotate(${data.now!.deg}deg)`;

            // 抬起
            if (!isMove) {

                // 偏移量很小时结束
                if (Math.abs(diffX + diffY) < 0.05) {
                    window.cancelAnimationFrame(animationFrameHandle);
                    opts.onAnimationFrameEnd!(data);
                    target.classList.remove(opts.dragAnimatingClassName!);
                    target.style.left = data.move!.targetOffsetX + 'px';
                    target.style.top = data.move!.targetOffsetY + 'px';
                    target.style.transform = '';
                    return;
                }

            }

            opts.onAnimationFrame!(data);
            animationFrameHandle = window.requestAnimationFrame(() => {
                transition();
            });

        };

        let pointermove = (evtMove: PointerEvent): void => {

            let pointerDiffX: number = evtMove.pageX - evtDown.pageX;
            let pointerDiffY: number = evtMove.pageY - evtDown.pageY;
            let targetOffsetX: number = data.start!.targetOffsetX;
            let targetOffsetY: number = data.start!.targetOffsetY;

            if (opts.xAxis) {

                targetOffsetX = data.start!.targetOffsetX + pointerDiffX;

                if (opts.restrictParentContainer) {
                    if (targetOffsetX <= 0) {
                        targetOffsetX = 0;
                    } else if (targetOffsetX > (target.parentNode as HTMLElement).clientWidth - target.clientWidth) {
                        targetOffsetX = (target.parentNode as HTMLElement).clientWidth - target.clientWidth;
                    }
                }

            }

            if (opts.yAxis) {

                targetOffsetY = data.start!.targetOffsetY + pointerDiffY;

                if (opts.restrictParentContainer) {
                    if (targetOffsetY <= 0) {
                        targetOffsetY = 0;
                    } else if (targetOffsetY > (target.parentNode as HTMLElement).clientHeight - target.clientHeight) {
                        targetOffsetY = (target.parentNode as HTMLElement).clientHeight - target.clientHeight;
                    }
                }

            }

            data.move = {
                evt: evtMove,
                pointerDiffX,
                pointerDiffY,
                targetOffsetX,
                targetOffsetY
            };

            if (!isMove && (!opts.diffSize || Math.abs(pointerDiffX) + Math.abs(pointerDiffY)) > opts.diffSize!) {
                target.classList.add(opts.draggingClassName!);
                target.classList.add(opts.dragAnimatingClassName!);
                opts.onDragStart!(evtMove, data);
                isMove = true;
                transition();
            }

            isMove && opts.onDragMove!(evtMove, data);

        };

        let pointerup = (evtUp: PointerEvent): void => {

            if (isMove) {

                isMove = false;

                data.end = {
                    evt: evtUp
                };

                target.classList.remove(opts.draggingClassName!);
                opts.onDragEnd!(evtUp, data);

            }

            window.removeEventListener('pointermove', pointermove);
            window.removeEventListener('pointerup', pointerup);

        };

        window.cancelAnimationFrame(animationFrameHandle);
        window.addEventListener('pointermove', pointermove);
        window.addEventListener('pointerup', pointerup);

    }

    target.addEventListener('pointerdown', pointerdown);

    return {

        enable() {
            enabled = true;
        },
        disable() {
            enabled = false;
        },
        destroy() {
            target.removeEventListener('pointerdown', pointerdown);
        },
        setOpts(newOpts: I_Drag2Opts) {
            Object.assign(opts, newOpts);
        }

    };
};
